﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.DotNet.Cli.Utils;

namespace Microsoft.DotNet.Cli.Commands.Run.LaunchSettings;

internal class LaunchSettingsManager
{
    private const string ProfilesKey = "profiles";
    private const string CommandNameKey = "commandName";
    private const string DefaultProfileCommandName = "Project";
    private static readonly IReadOnlyDictionary<string, ILaunchSettingsProvider> _providers;

    static LaunchSettingsManager()
    {
        _providers = new Dictionary<string, ILaunchSettingsProvider>
        {
            { ProjectLaunchSettingsProvider.CommandNameValue, new ProjectLaunchSettingsProvider() }
        };
    }

    public static LaunchSettingsApplyResult TryApplyLaunchSettings(string launchSettingsPath, string? profileName = null)
    {
        var launchSettingsJsonContents = File.ReadAllText(launchSettingsPath);
        try
        {
            var jsonDocumentOptions = new JsonDocumentOptions
            {
                CommentHandling = JsonCommentHandling.Skip,
                AllowTrailingCommas = true,
            };

            using (var document = JsonDocument.Parse(launchSettingsJsonContents, jsonDocumentOptions))
            {
                var model = document.RootElement;

                if (model.ValueKind != JsonValueKind.Object || !model.TryGetProperty(ProfilesKey, out var profilesObject) || profilesObject.ValueKind != JsonValueKind.Object)
                {
                    return new LaunchSettingsApplyResult(false, CliCommandStrings.LaunchProfilesCollectionIsNotAJsonObject);
                }

                var selectedProfileName = profileName;
                JsonElement profileObject;
                if (string.IsNullOrEmpty(profileName))
                {
                    var firstProfileProperty = profilesObject.EnumerateObject().FirstOrDefault(IsDefaultProfileType);
                    selectedProfileName = firstProfileProperty.Value.ValueKind == JsonValueKind.Object ? firstProfileProperty.Name : null;
                    profileObject = firstProfileProperty.Value;
                }
                else // Find a profile match for the given profileName
                {
                    IEnumerable<JsonProperty> caseInsensitiveProfileMatches = [.. profilesObject
                        .EnumerateObject() // p.Name shouldn't fail, as profileObject enumerables here are only created from an existing JsonObject
                        .Where(p => string.Equals(p.Name, profileName, StringComparison.OrdinalIgnoreCase))];

                    if (caseInsensitiveProfileMatches.Count() > 1)
                    {
                        throw new GracefulException(CliCommandStrings.DuplicateCaseInsensitiveLaunchProfileNames,
                            string.Join(",\n", caseInsensitiveProfileMatches.Select(p => $"\t{p.Name}").ToArray()));
                    }
                    else if (!caseInsensitiveProfileMatches.Any())
                    {
                        return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.LaunchProfileDoesNotExist, profileName));
                    }
                    else
                    {
                        profileObject = profilesObject.GetProperty(caseInsensitiveProfileMatches.First().Name);
                    }

                    if (profileObject.ValueKind != JsonValueKind.Object)
                    {
                        return new LaunchSettingsApplyResult(false, CliCommandStrings.LaunchProfileIsNotAJsonObject);
                    }
                }

                if (profileObject.ValueKind == default)
                {
                    foreach (var prop in profilesObject.EnumerateObject())
                    {
                        if (prop.Value.ValueKind == JsonValueKind.Object)
                        {
                            if (prop.Value.TryGetProperty(CommandNameKey, out var commandNameElement) && commandNameElement.ValueKind == JsonValueKind.String)
                            {
                                if (commandNameElement.GetString() is { } commandNameElementKey &&  _providers.ContainsKey(commandNameElementKey))
                                {
                                    profileObject = prop.Value;
                                    break;
                                }
                            }
                        }
                    }
                }

                if (profileObject.ValueKind == default)
                {
                    return new LaunchSettingsApplyResult(false, CliCommandStrings.UsableLaunchProfileCannotBeLocated);
                }

                if (!profileObject.TryGetProperty(CommandNameKey, out var finalCommandNameElement)
                    || finalCommandNameElement.ValueKind != JsonValueKind.String)
                {
                    return new LaunchSettingsApplyResult(false, CliCommandStrings.UsableLaunchProfileCannotBeLocated);
                }

                string? commandName = finalCommandNameElement.GetString();
                if (!TryLocateHandler(commandName, out ILaunchSettingsProvider? provider))
                {
                    return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.LaunchProfileHandlerCannotBeLocated, commandName));
                }

                return provider.TryGetLaunchSettings(selectedProfileName, profileObject);
            }
        }
        catch (JsonException ex)
        {
            return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.DeserializationExceptionMessage, launchSettingsPath, ex.Message));
        }
    }

    private static bool TryLocateHandler(string? commandName, [NotNullWhen(true)]out ILaunchSettingsProvider? provider)
    {
        if (commandName == null)
        {
            provider = null;
            return false;
        }

        return _providers.TryGetValue(commandName, out provider);
    }

    private static bool IsDefaultProfileType(JsonProperty profileProperty)
    {
        if (profileProperty.Value.ValueKind != JsonValueKind.Object
            || !profileProperty.Value.TryGetProperty(CommandNameKey, out var commandNameElement)
            || commandNameElement.ValueKind != JsonValueKind.String)
        {
            return false;
        }

        return string.Equals(commandNameElement.GetString(), DefaultProfileCommandName, StringComparison.Ordinal);
    }
}
